with bank_account;
use bank_account;
with currency;
use currency;
with ada.text_io;
with ada.float_text_io;
with v_string;
use v_string;
with authentication;
use authentication;

procedure bank is

    type commands is (login, logout, help, setup, create, delete, quit, deposit, withdraw, transfer, balance, none);
    type states is (logged_in, logged_out);

    package t_io renames ada.text_io;
    package f_io renames ada.float_text_io;
    package com_io is new t_io.enumeration_io (commands);
    package acc renames bank_account;


    command : commands;
    state : states;

    state_error : exception;
    input_error : exception;
    no_bank_account : exception;
    create_error : exception;
    delete_user_error : exception;
    user_not_found : exception;
    not_enough_funds : exception;
    password_error : exception;

    procedure displayHelp (helpCommand : in commands) is
	args : vstring;
	helped: boolean;
    begin
	t_io.new_line;
	helped := false;
	if t_io.end_of_line = false then
	    vget(args);
	    t_io.skip_line;
	end if;
	if vcompare(args, "quit") or helpCommand = quit then
	    t_io.put_line("quit");
	    t_io.put_line("exits program");
	    helped := true;
	elsif vcompare(args, "login") or helpCommand = login  then
	    if state = logged_out then
		t_io.put_line("login [username] [password]");
		t_io.put_line("used to login to banking system");
		helped := true;
	    end if;
	elsif vcompare(args, "logout") or helpCommand = logout  then
	    if state = logged_in then
		t_io.put_line("logout ");
		t_io.put_line("used to logout of banking system");
		helped := true;
	    end if;
	elsif vcompare(args, "setup") or helpCommand = setup  then
	    if state = logged_in then
		t_io.put_line("setup ");
		t_io.put_line("used to create an account in our banking system");
		helped := true;
	    end if;
	elsif vcompare(args, "deposit") or helpCommand = deposit then
	    if state = logged_in then
		t_io.put_line("deposit [amount]");
		t_io.put_line("used to deposit into currently logged in account");
		helped := true;
	    end if;
	elsif vcompare(args, "withdraw") or helpCommand = withdraw  then
	    if state = logged_in then
		t_io.put_line("withdraw [amount]");
		t_io.put_line("used to withdraw from currently logged in account");
		helped := true;
	    end if;
	elsif vcompare(args, "transfer") or helpCommand = transfer then
	   if state = logged_in then
		t_io.put_line("transfer [username] [amount]");
		t_io.put_line("used to transfer funds to another account via username");
		helped := true;
	    end if;
	elsif vcompare(args, "balance") or helpCommand = balance then
	    if state = logged_in then
		t_io.put_line("balance");
		t_io.put_line("used to view current balance");
		helped := true;
	    end if;
	end if;
	if helped = false then
	    t_io.put_line("Available commands:");
	    if state = logged_out then
		t_io.put_line(" -login");
	    else
		t_io.put_line(" -logout");
		t_io.put_line(" -setup");
		t_io.put_line(" -deposit");
		t_io.put_line(" -withdraw");
		t_io.put_line(" -transfer");
		t_io.put_line(" -balance");
	    end if;
	    t_io.put_line(" -quit");
	end if;
	t_io.new_line;
    end displayHelp;


    
    args : vstring;
    


begin

    --main loop

    state := logged_out;

    loop
	begin
	    t_io.put("Enter a command > "); 
	    command := none;
	    com_io.get(command);
	    case command is
		when quit => exit;
		when help => displayHelp(none);
		when login => 
		    if state = logged_in then
			raise state_error;
		    end if;
		    GET_ARGS:
		    declare
			username, password : vstring;
		    begin
			vget_line(args);
			username := vgetToken(args, 1);
			password := vgetToken(args, 2);
			if authenticate(username, password) = true then
			    state := logged_in;
			    if selectAccount(getCurrentID) = false then
				raise no_bank_account;
			    end if;
			else
			    t_io.put_line("Invalid username/password combination");
			end if;
		    exception
			when EOL_error | token_not_found | constraint_error  => 
			    raise input_error;
		    end GET_ARGS;

		when logout => 
		    if state = logged_out then
			raise state_error;
		    end if;
		    if t_io.end_of_line = false then
			raise input_error;
		    end if;
		    state := logged_out;
		    unauthenticate;
		    deselectAccount;
		when setup =>
		    if state = logged_out then
			raise state_error;
		    end if;
		    if t_io.end_of_line = false then
			raise input_error;
		    end if;
		    if createAccount (getCurrentID) = false then
			raise create_error;
		    end if;
		    if selectAccount(getCurrentID) = false then
			raise no_bank_account;
		    end if;
		when create =>
		    if t_io.end_of_line = true then
			raise input_error;
		    end if;
		    declare
			args, username, password, admin : vstring;
		    begin
			vget_line(args);
			admin := vgetToken(args, 1);
			username := vgetToken(args, 2);
			password := vgetToken(args, 3);
			if vcompare(admin, toVstring("admin")) = false then
			    raise password_error;
			end if;
			createUser(username, password);
		    end;
		when delete =>
		    if t_io.end_of_line = true then
			raise input_error;
		    end if;
		    declare
			args, username, admin : vstring;
		    begin
			vget_line(args);
			admin := vgetToken(args, 1);
			username := vgetToken(args, 2);
			if vcompare(admin, toVstring("admin")) = false then
			    raise password_error;
			end if;
			if deleteUser(getIDFromUsername(username)) = false then
			    raise delete_user_error;
			end if;
		    exception
			when authentication.user_not_found =>
			    raise user_not_found;
		    end;
		when deposit =>
		    if state = logged_out then
			raise state_error;
		    end if;
		    if selectAccount(getCurrentID) = false then
			raise no_bank_account;
		    end if;
		    if t_io.end_of_line = true then
			raise input_error;
		    end if;
		    if selectAccount(getCurrentID) = false then
			raise no_bank_account;
		    end if;
		    declare
			float_amount : float;
			dollar_amount : dollar;
			c : Character;
			EOL : Boolean;
		    begin
			loop
			    t_io.look_ahead(c, EOL);
			    exit when c /= ' ' and c /= '$';
			    t_io.get(c);
			    exit when c = '$';
			end loop;
			f_io.get(float_amount);
			if float_amount < 0.0 then
			    raise input_error;
			end if;
			dollar_amount := float_to_dollar(float_amount);
			t_io.put_line("new balance:");
			print(deposit(dollar_amount));
			t_io.new_line;
		    exception
			when t_io.data_error =>
			    raise input_error;
		    end;
		when withdraw => 
		    if state = logged_out then
			raise state_error;
		    end if;
		    if t_io.end_of_line then
			raise input_error;
		    end if;
		    if selectAccount(getCurrentID) = false then
			raise no_bank_account;
		    end if;
		    declare
			float_amount : float;
			dollar_amount : dollar;
			c : Character;
			EOL : Boolean;
		    begin
			loop
			    t_io.look_ahead(c, EOL);
			    exit when c /= ' ' and c/= '$';
			    t_io.get(c);
			    exit when c = '$';
			end loop;
			if t_io.end_of_line then
			    raise input_error;
			end if;
			f_io.get(float_amount);
			if float_amount < 0.0 then
			    raise input_error;
			end if;
			dollar_amount := float_to_dollar(float_amount);
			t_io.put_line("new balance:");
			print(withdraw(dollar_amount));
			t_io.new_line;
		    exception
			when t_io.data_error =>
			    raise input_error;
		    end;
		when transfer => 
		    if state = logged_out then
			raise state_error;
		    end if;
		    if selectAccount(getCurrentID) = false then
			t_io.skip_line;
			raise no_bank_account;
		    end if;
		    if t_io.end_of_line = true then
			raise input_error;
		    end if;
		    declare
			username : vstring;
			currentID, userID : natural;
			float_amount : float;
			dollar_amount : dollar;
			okay, EOL : boolean;
			c : Character;
		    begin
			vget(username);
			loop
			    t_io.look_ahead(c, EOL);
			    exit when c /= ' ' and c/= '$';
			    t_io.get(c);
			    exit when c = '$';
			end loop;
			if t_io.end_of_line then
			    raise input_error;
			end if;
			f_io.get(float_amount);
			if float_amount < 0.0 then
			    raise input_error;
			end if;
			dollar_amount := float_to_dollar(float_amount);
			if dollar_amount > getBalance then 
			    raise not_enough_funds;
			end if;
			currentID := getCurrentID;
			userID := getIDFromUsername(username);
			if selectAccount(userID) = false then
			    vput(username);
			    t_io.put_line(" does not have a bank account set up");
			    okay := selectAccount(currentID);
			else
			    t_io.put("new balance for ");
			    vput(username);
			    t_io.put(" is ");
			    print(deposit(dollar_amount));
			    okay := selectAccount(currentID);
			    t_io.new_line;
			    t_io.put("your new balance is ");
			    print(withdraw(dollar_amount));
			    t_io.new_line;
			end if;
		    end;
		when balance => 
		    if state = logged_out then
			raise state_error;
		    end if;
		    if t_io.end_of_line = false then
			raise input_error;
		    end if;
		    if selectAccount(getCurrentID) = false then
			raise no_bank_account;
		    else
			t_io.put_line("current balance:");
			print(getBalance);
			t_io.new_line;
		    end if;
		when others =>
		    displayHelp(none);
	    end case;
        exception
	    when state_error | t_io.data_error =>
		displayHelp (command);
	    when input_error =>
		displayHelp (command);
	    when no_bank_account =>
		t_io.put_line("You have not created a bank account with us");
	    when create_error =>
		t_io.put_line("You have already created a bank account with us");
	    when not_enough_funds =>
		t_io.put_line("not enough funds");
	    when user_not_found =>
		t_io.put_line("user not found");
	    when password_error =>
		t_io.put_line("wrong admin password");
	    when create_user_error =>
		t_io.put_line("could not create user");
	    when delete_user_error =>
		t_io.put_line("could not delete user");
	end;

    end loop;



end bank;
